﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Globalization;
using System.Drawing;
using System.IO;
using System.ComponentModel;
using System.Drawing.Imaging;
using MonoStrategy;

namespace MonoStrategy
{
    /// <summary>
    /// Interaction logic for AnimLibraryTab.xaml
    /// </summary>
    public partial class AnimLibraryTab : UserControl
    {
        private void BTN_GfxAppendSequence_Click(object sender, RoutedEventArgs e)
        {
            GfxImportSequence(true);
        }

        private void BTN_GfxImportSequence_Click(object sender, RoutedEventArgs args)
        {
            GfxImportSequence(false);
        }

        private void GfxImportSequence(bool inCanAppend)
        {
            var gfxSequence = MainWindow.Instance.CurrentSequence;

            if ((gfxSequence == null) || (gfxSequence.Frames.Count() == 0))
            {
                Log.LogMessageModal("No GFX sequence available or it does not contain any frame.");

                return;
            }

            // check for CTS-Sequence
            GFXSequence color, torso, shadow;
            int minX = Int32.MaxValue, minY = Int32.MaxValue;
            List<Animation> newAnims = new List<Animation>();

            if (!PrepareCTSSequence(gfxSequence, out color, out torso, out shadow))
            {
                Animation anim;

                if (inCanAppend && (TCurrentAnim != null) && TCurrentSet.Animations.Contains(TCurrentAnim))
                {
                    // append to animation
                    anim = TCurrentAnim;
                }
                else
                {
                    // allocate animation
                    int i = 0;

                    do
                    {
                        i++;

                        if (TCurrentSet.Animations.Any(e => e.Name == "GfxAnim_" + i))
                            continue;

                        anim = TCurrentSet.AddAnimation("GfxAnim_" + i);

                        break;
                    } while (true);
                }

                // fill with frames
                foreach (var gfxFrame in gfxSequence.Frames)
                {
                    var frame = anim.AddFrame();

                    InitGfxFrame(frame, gfxFrame);

                    minX = Math.Min(minX, frame.OffsetX);
                    minY = Math.Min(minY, frame.OffsetY);
                }

                newAnims.Add(anim);
            }
            else
            {
                String[] animNames = new String[] { "Color", "Torso", "Shadow" };
                GFXSequence[] seqs = new GFXSequence[] { color, torso, shadow };

                for (int x = 0; x < 3; x++)
                {
                    Animation anim = null;

                    if (TCurrentSet.Animations.Any(e => {if(e.Name == animNames[x]) {anim = e; return true; } return false; }))
                    {
                        if(!inCanAppend)
                            throw new ArgumentException("The current animation set already contain an animation called \"" + animNames[x] + "\"!");

                        foreach (var frame in anim.Frames)
                        {
                            minX = Math.Min(minX, frame.OriginalOffsetX);
                            minY = Math.Min(minY, frame.OriginalOffsetY);
                        }
                    }
                    else
                    {
                        anim = TCurrentSet.AddAnimation(animNames[x]);
                    }

                    newAnims.Add(anim);
                    anim.RenderIndex = 2 - x;

                    for (int z = 0; z < gfxSequence.Frames.Count; z++)
                    {
                        var gfxFrame = seqs[x].Frames[z];
                        var frame = anim.AddFrame();

                        InitGfxFrame(frame, gfxFrame);

                        minX = Math.Min(minX, frame.OriginalOffsetX);
                        minY = Math.Min(minY, frame.OriginalOffsetY);
                    }
                }
            }


            // normalize offsets to origin
            foreach (var anim in newAnims)
            {
                foreach (var frame in anim.Frames)
                {
                    frame.OffsetX = frame.OriginalOffsetX - minX;
                    frame.OffsetY = frame.OriginalOffsetY - minY;
                }

                anim.ComputeDimension();
            }
        }

        private void InitGfxFrame(AnimationFrame inTarget, GFXFrame inSource)
        {
            inTarget.SetBitmap(inSource.Image);

            inTarget.Width = inSource.Width;
            inTarget.Height = inSource.Height;
            inTarget.OriginalOffsetX = inTarget.OffsetX = inSource.OffsetX;
            inTarget.OriginalOffsetY = inTarget.OffsetY = inSource.OffsetY;

            // save metadata
            inTarget.GFXSequence = inSource.Sequence.Index;
            inTarget.GFXFrame = inSource.Index;
            inTarget.GFXFileChecksum = inSource.Sequence.File.Checksum;
        }

        private bool PrepareCTSSequence(GFXSequence gfxSequence, out GFXSequence color, out GFXSequence torso, out GFXSequence shadow)
        {
            shadow = torso = color = null;

            // validate sequences
            var ObjectSeqs = MainWindow.Instance.ObjectSeqs;
            var TorsoSeqs = MainWindow.Instance.TorsoSeqs;
            var ShadowSeqs = MainWindow.Instance.ShadowSeqs;
            int seqIndex;

            if ((seqIndex = ObjectSeqs.IndexOf(gfxSequence)) >= 0) { }
            else if ((seqIndex = TorsoSeqs.IndexOf(gfxSequence)) >= 0) { }
            else if ((seqIndex = ShadowSeqs.IndexOf(gfxSequence)) >= 0) { }
            else
                return false;

            if ((ObjectSeqs.Count != TorsoSeqs.Count) || (ObjectSeqs.Count != ShadowSeqs.Count))
                return false;

            color = ObjectSeqs[seqIndex];
            torso = TorsoSeqs[seqIndex];
            shadow = ShadowSeqs[seqIndex];

            if ((color.Frames.Count != torso.Frames.Count) || (color.Frames.Count != shadow.Frames.Count))
                return false;

            return true;
        }

        private void BTN_GfxImportDirMotion_Click(object sender, RoutedEventArgs args)
        {
            var gfxSequence = MainWindow.Instance.CurrentSequence;

            if ((gfxSequence == null) || (gfxSequence.Frames.Count() == 0))
            {
                Log.LogMessageModal("No GFX sequence available or it does not contain any frame.");

                return;
            }

            // validate sequences
            GFXSequence color, torso, shadow;

            if (!PrepareCTSSequence(gfxSequence, out color, out torso, out shadow))
                throw new ArgumentException("Selected GFX sequence is not a color-, torso- or shadow-seqence!");

            if ((color.Frames.Count % 6) != 0)
                throw new ArgumentException("Color sequence's frame count is not dividable by six.");

            String[] angleStrings = new String[] { "Angle_225", "Angle_270", "Angle_315", "Angle_045", "Angle_090", "Angle_135", };

            if (TCurrentClass.Sets.Count > 0)
            {
                // check if this is a pure directed motion
                for (int i = 0; i < 6; i++)
                {
                    if (!TCurrentClass.HasSet(angleStrings[i]) || !TCurrentClass.HasSet("Frozen" + angleStrings[i]))
                        throw new InvalidOperationException("The current class is not empty and also not a directed motion.");
                }

                // check if we have access to original offsets
                foreach (var set in TCurrentClass.Sets)
                {
                    foreach (var anim in set.Animations)
                    {
                        foreach (var frame in anim.Frames)
                        {
                            if ((frame.OriginalOffsetX == Int32.MinValue) || (frame.OriginalOffsetY == Int32.MinValue))
                                throw new InvalidOperationException("To append direction motion, all existing frames need to have valid original offsets!");
                        }
                    }
                }
            }

            // allocate animation sets
            AnimationSet[] sets = new AnimationSet[6];
            String[] animNames = new String[]{ "Color", "Torso", "Shadow" };
            GFXSequence[] seqs = new GFXSequence[]{ color, torso, shadow };
            int stride = color.Frames.Count / 6;
            int minX = Int32.MaxValue, minY = Int32.MaxValue;

            if (stride == 13)
            {
                /*
                 * Usually there are 12 frames for each direction for settler motions.
                 * But sometimes the sequence is followed by another 6 frames, one for
                 * each direction representing its idle state.
                 * 
                 * In this step we will just ignore the last six frames...
                 */
                stride = 12;
            }

            for (int i = 0, offset = 0; i < 6; i++, offset += stride)
            {
                AnimationSet set;
                
                if(TCurrentClass.HasSet(angleStrings[i]))
                    set = TCurrentClass.FindSet(angleStrings[i]);
                else
                    set = TCurrentClass.AddAnimationSet(angleStrings[i]);

                sets[i] = set;
                set.DurationMillis = 1000;

                for (int x = 0; x < 3; x++)
                {
                    Animation anim;

                    if (set.HasAnimation(animNames[x]))
                        anim = set.FindAnimation(animNames[x]);
                    else
                        anim = set.AddAnimation(animNames[x]);

                    anim.RenderIndex = 2 - x;

                    for (int z = offset; z < offset + stride; z++)
                    {
                        var gfxFrame = seqs[x].Frames[z];
                        var frame = anim.AddFrame();

                        InitGfxFrame(frame, gfxFrame);

                        minX = Math.Min(minX, frame.OffsetX);
                        minY = Math.Min(minY, frame.OffsetY);                        
                    }
                }
            }

            if (color.Frames.Count == 78)
            {
                // use special frames for frozen images
                for (int i = 0; i < 6; i++)
                {
                    AnimationSet set;

                    if (TCurrentClass.HasSet("Frozen" + angleStrings[i]))
                        set = TCurrentClass.FindSet("Frozen" + angleStrings[i]);
                    else
                        set = TCurrentClass.AddAnimationSet("Frozen" + angleStrings[i]);

                    for (int x = 0; x < 3; x++)
                    {
                        Animation anim;

                        if (set.HasAnimation(animNames[x]))
                            anim = set.FindAnimation(animNames[x]);
                        else
                            anim = set.AddAnimation(animNames[x]);

                        var gfxFrame = seqs[x].Frames[72 + i];
                        var frame = anim.AddFrame();

                        anim.RenderIndex = 2 - x;

                        InitGfxFrame(frame, gfxFrame);

                        minX = Math.Min(minX, frame.OffsetX);
                        minY = Math.Min(minY, frame.OffsetY);
                    }
                }
            }
            else
            {
                // use first stride image for frozen frames
                for (int i = 0, offset = 0; i < 6; i++, offset += stride)
                {
                    AnimationSet set;

                    if (TCurrentClass.HasSet("Frozen" + angleStrings[i]))
                        set = TCurrentClass.FindSet("Frozen" + angleStrings[i]);
                    else
                        set = TCurrentClass.AddAnimationSet("Frozen" + angleStrings[i]);

                    for (int x = 0; x < 3; x++)
                    {
                        Animation anim;

                        if (set.HasAnimation(animNames[x]))
                            anim = set.FindAnimation(animNames[x]);
                        else
                            anim = set.AddAnimation(animNames[x]);

                        var gfxFrame = seqs[x].Frames[offset];
                        var frame = anim.AddFrame();

                        anim.RenderIndex = 2 - x;

                        InitGfxFrame(frame, gfxFrame);

                        minX = Math.Min(minX, frame.OffsetX);
                        minY = Math.Min(minY, frame.OffsetY);
                    }
                }
            }

            // normalize offsets to origin
            foreach (var set in TCurrentClass.Sets)
            {
                foreach (var anim in set.Animations)
                {
                    foreach (var frame in anim.Frames)
                    {
                        frame.OffsetX = frame.OriginalOffsetX - minX;
                        frame.OffsetY = frame.OriginalOffsetY - minY;
                    }

                    anim.ComputeDimension();
                }
            }
        }

        private void BTN_GenFrozenFrames_Click(object sender, RoutedEventArgs e)
        {
            // derive from existing animation frames
            String[] angleStrings = new String[] { "Angle_225", "Angle_270", "Angle_315", "Angle_045", "Angle_090", "Angle_135", };

            foreach (var angle in angleStrings)
            {
                if (TCurrentClass.ContainsSet("Angle" + angle))
                    throw new InvalidOperationException("Current animation class is not a directed motion!");

                if (TCurrentClass.ContainsSet("Frozen" + angle))
                    throw new InvalidOperationException("Current animation class already contains frozen frames!");
            }

            foreach (var angle in angleStrings)
            {
                AnimationSet frozen = TCurrentClass.AddAnimationSet("Frozen" + angle);

                foreach (var anim in TCurrentClass.FindSet(angle).Animations)
                {
                    var frozenAnim = frozen.AddAnimation(anim.Name);

                    frozenAnim.FrozenFrame = frozenAnim.AddFrame();
                    frozenAnim.IsFrozen = true;
                    frozenAnim.IsVisible = true;
                    frozenAnim.OffsetX = anim.OffsetX;
                    frozenAnim.OffsetY = anim.OffsetY;
                    frozenAnim.RenderIndex = anim.RenderIndex;

                    anim.FirstFrame.Clone(frozenAnim.FrozenFrame);

                    frozenAnim.ComputeDimension(false);
                }

                frozen.ComputeDimension();
            }
        }
    }
}
